/*=============================================================================
DeferredShading.h : Deferred shading pipeline
Copyright (c) 2001 Crytek Studios. All Rights Reserved.

=============================================================================*/

#ifndef _DEFERREDSHADING_H_
#define _DEFERREDSHADING_H_

struct IVisArea;

enum EDecalType
{
  DTYP_DARKEN,
  DTYP_BRIGHTEN,
  DTYP_ALPHABLEND,
  DTYP_ALPHABLEND_AND_BUMP,
  DTYP_ALPHABLEND_SPECULAR,
  DTYP_DARKEN_LIGHTBUF,
  DTYP_NUM,
};

class CTexPoolAtlas;

struct SShadowAllocData
{
	int			m_lightID;
	uint16	m_blockID;
	uint8		m_side;
	uint8		m_frameID;

	void Clear(){ m_blockID = 0xFFFF; m_lightID = -1; m_frameID = 0;}
	bool isFree() { return (m_blockID == 0xFFFF) ? true:false; }

	SShadowAllocData(){ Clear(); }
	~SShadowAllocData(){ Clear(); }

};

class CDeferredShading
{
public:

	static inline bool IsValid()
	{
		return m_pInstance != NULL;
	}

	static CDeferredShading &Instance()
	{
		return (*m_pInstance);
	}

	void Render();  
	void SetupPasses();
	void SetupGlobalConsts();

	//shadows
	void ResolveCurrentBuffers();
	void RestoreCurrentBuffers();
	bool PackAllShadowFrustums(TArray<SRenderLight>& arrLights, bool bPreLoop);
	bool PackToPool( CPowerOf2BlockPacker *pBlockPack, SRenderLight& light, const int nLightID, const int nFirstCandidateLight, bool bClearPool);

	bool AmbientPass(const SRenderLight *pGlobalCubemap, bool* const bOutdoorVisible);
	void NightVisionAmbientPass();
	bool DeferredDecalPass( SDeferrredDecal& rDecal, EDecalType decalType, uint indDecal);
	void DrawDeferredDecals(EDecalType decalType);
	bool ShadowLightPasses(const SRenderLight& light, const int nLightID);
	void DrawDecalVolume(const SDeferrredDecal& rDecal, Matrix44A& mDecalLightProj, ECull volumeCull);
	void DrawLightVolume(const SRenderLight *const __restrict pDL);
	bool GIPass();
	void LightPass( const SRenderLight *const __restrict pDL, bool bForceStencilDisable = false);
	void DeferredCubemaps( const TArray<SRenderLight>& rCubemaps, const uint32 nStartIndex = 0 );
	void DeferredCubemapPass( const SRenderLight *const __restrict pDL );
	void ScreenSpaceReflectionPass();
	void DirectionalOcclusionPass();
	void DeferredLights( TArray<SRenderLight>& rLights, bool bCastShadows );

	void CreateDeferredMaps();
	void DestroyDeferredMaps();
	void Release();
	void Debug();

	uint32 AddLight(const CDLight &pDL, float fMult);
	inline uint32 AddVisArea( IVisArea *pVisArea );
	void AddGIClipVolume( IRenderMesh *pClipVolume, const Matrix34& mxTransform );
	inline void ResetLights();
	inline void ResetVisAreas();

	// Renderer must be flushed
	void ResetAllLights();
	void ResetAllVisAreas();

	// called in between levels to free up memory
	void ReleaseData();

	uint32 GetVisAreaStencilRef( const IVisArea *pVisArea );

	TArray<SRenderLight>& GetLights(const int nThreadID, const int nCurRecLevel, const eDeferredLightType eType = eDLT_DeferredLight);
	uint32 GetLightsNum(const eDeferredLightType eType);

	inline uint32 GetLightsCount() const 
	{
		return m_nLightsProcessedCount;
	}

	inline Vec4 GetLightDepthBounds( const SRenderLight *pDL ) const
	{
		return GetLightDepthBounds(pDL->m_Origin, pDL->m_fRadius);
	}

	inline Vec4 GetLightDepthBounds( const Vec3 & vCenter, float fRadius ) const
	{
		if( !CRenderer::CV_r_DeferredShadingDepthBoundsTest ) 
			return Vec4(0.0f, 0.0f, 1.0f, 1.0f);

		float fMinZ = 0.0f, fMaxZ = 1.0f;
		float fMinW = 0.0f, fMaxW = 1.0f;

		Vec3 pBounds = m_pCamFront * fRadius; 
		Vec3 pMax = vCenter - pBounds;
		Vec3 pMin = vCenter + pBounds;

		fMinZ = m_mViewProj.m20 * pMin.x + m_mViewProj.m21 * pMin.y + m_mViewProj.m22 * pMin.z + m_mViewProj.m23;
		fMinW = m_mViewProj.m30 * pMin.x + m_mViewProj.m31 * pMin.y + m_mViewProj.m32 * pMin.z + m_mViewProj.m33;

		float fMinDivisor = (float)__fsel(-fabsf(fMinW), 1.0f, fMinW);
		fMinZ = (float)__fsel(-fabsf(fMinW), 1.0f, fMinZ / fMinDivisor);
		fMinZ = (float)__fsel(fMinW, fMinZ, 0.0f);

		fMinZ = clamp_tpl( fMinZ, 0.01f, 1.f );

		fMaxZ = m_mViewProj.m20 * pMax.x + m_mViewProj.m21 * pMax.y + m_mViewProj.m22 * pMax.z + m_mViewProj.m23;
		fMaxW = m_mViewProj.m30 * pMax.x + m_mViewProj.m31 * pMax.y + m_mViewProj.m32 * pMax.z + m_mViewProj.m33; 
		float fMaxDivisor = (float)__fsel(-fabsf(fMaxW), 1.0f, fMaxW);
		fMaxZ = (float)__fsel(-fabsf(fMaxW), 1.0f, fMaxZ / fMaxDivisor);
		fMaxZ = (float)__fsel(fMaxW, fMaxZ, 0.0f);

		fMaxZ = clamp_tpl( fMaxZ, fMinZ, 1.f );

		return Vec4( fMinZ, max(fMinW, 0.000001f ), fMaxZ, max(fMaxW, 0.000001f ));
	}

	void GetScissors(const Vec3 & vCenter, float fRadius, short & sX, short & sY, short & sWidth, short & sHeight) const;
	void SetupScissors(bool bEnable, uint16 x, uint16 y, uint16 w, uint16 h) const;

	const Matrix44A& GetCameraProjMatrix() const { return m_mViewProj; }

	// Get a light intensity multiplier to preserve lighting consistency for old and new SSAO
	static float GetAONormalizationCoef();

private:

	CDeferredShading()
	{
		m_pShader = 0;
		m_pTechName = "DeferredLightPass";
		m_pAmbientOutdoorTechName = "AmbientOutdoor";
		m_pAmbientVisAreaTechName = "AmbientVisArea";
		m_pCubemapsTechName = "DeferredCubemapPass";
		m_pCubemapsVolumeTechName = "DeferredCubemapVolumePass"; 
		m_pReflectionTechName = "ReflectionPass";
		m_pDebugTechName = "Debug";
		m_pDeferredDecalTechName = "DeferredDecal";
		m_pLightVolumeTechName = "DeferredLightVolume";
		m_pDeferredSceneTechName = "DeferredScenePass";

		m_pParamPalette = "g_palette";
		m_pParamLightPos = "g_LightPos";
		m_pParamCameraMatrix = "g_mCamera";
		m_pParamLightProjMatrix = "g_mLightProj";
		m_pParamLightProjParams = "g_LightProjParams";
		m_pGeneralParams = "g_GeneralParams";    
		m_pParamLightDiffuse = "g_LightDiffuse";        

		m_pDiffuseAccRT = CTexture::s_ptexCurrentSceneDiffuseAccMap;
		m_pSpecularAccRT = CTexture::s_ptexSceneSpecularAccMap;
		m_pNormalsRT = CTexture::s_ptexSceneNormalsMap;
		m_pDepthRT = CTexture::s_ptexZTarget;
		m_pMSAAMaskRT = CTexture::s_ptexBackBuffer;

		m_nLightsProcessedCount = 0;

		m_nCurLighID = -1;

		m_bSpecularState = false;

		m_pSSAORT = NULL;

		m_nShadowPoolSize = 0;

		for(int i = 0; i < 4; ++i)
			m_prevViewProj[i].SetIdentity();

		m_nRenderState = GS_BLSRC_ONE|GS_BLDST_ONE;

		for(int i=0; i<RT_COMMAND_BUF_COUNT; ++i)
			for(int j=0; j<MAX_REND_RECURSION_LEVELS; ++j)
			{
				m_nVisAreasCount[i][j] = 0;
				m_nVisAreasGICount[i][j] = 0;
				m_nVisAreasGIRef[i][j] = 0;
			}
	}

	~CDeferredShading()
	{
		Release();
	}

	// Allow disable mrt usage: for double zspeed and on other passes less fillrate hit
	void SpecularAccEnableMRT( bool bEnable );

private:

	struct SVisAreaData
	{		
		SVisAreaData(): cAmbient(ZERO), nStencilRef(0), bIsConnectedToOutdoor(false), bIsIgnoringGI(false)
		{
		};

		AABB pAABB;
		Vec3 cAmbient;		
		uint32 nStencilRef;
		bool bIsConnectedToOutdoor;
		bool bIsIgnoringGI;		
	};

	// Vis areas for current view 
	typedef std::map< const IVisArea *, SVisAreaData > VisAreaDataMap;    
	typedef VisAreaDataMap::const_iterator VisAreaDataMapItor;
	VisAreaDataMap m_pVisAreas[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];

	uint32 m_nVisAreasCount[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];
	uint32 m_nVisAreasGICount[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];
	uint32 m_nVisAreasGIRef[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];

	struct SClipShape
	{
		IRenderMesh*	pShape;
		Matrix34			mxTransform;
		SClipShape() : pShape(NULL), mxTransform(Matrix34::CreateIdentity()) {}
		SClipShape(IRenderMesh*	_pShape, const Matrix34& _mxTransform) : pShape(_pShape), mxTransform(_mxTransform) { }
	};

	// Clip volumes for GI for current view 
	TArray<SClipShape> m_vecGIClipVolumes[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];

	// Deferred passes common 
	TArray<SRenderLight> m_pLights[eDLT_NumLightTypes][RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];

	Vec3 m_pCamPos;
	Vec3 m_pCamFront;
	float m_fCamFar;
	float m_fCamNear;

	float m_fRatioWidth;
	float m_fRatioHeight;

	CShader  *m_pShader; 
	CCryNameTSCRC m_pDeferredDecalTechName;
	CCryNameTSCRC m_pLightVolumeTechName;
	CCryNameTSCRC m_pTechName;
	CCryNameTSCRC m_pAmbientOutdoorTechName;
	CCryNameTSCRC m_pAmbientVisAreaTechName;
	CCryNameTSCRC m_pCubemapsTechName;
	CCryNameTSCRC m_pCubemapsVolumeTechName;
	CCryNameTSCRC m_pReflectionTechName;
	CCryNameTSCRC m_pDebugTechName;
	CCryNameTSCRC m_pDeferredSceneTechName;
	CCryNameR m_pParamLightPos;
	CCryNameR m_pParamPalette;
	CCryNameR m_pParamCameraMatrix;
	CCryNameR m_pParamLightDiffuse;        
	CCryNameR m_pParamLightProjMatrix;
	CCryNameR m_pParamLightProjParams;
	CCryNameR m_pGeneralParams;

	Matrix44A m_mViewProj;
	Matrix44A m_pViewProjI;
	Matrix44A m_pView;  

	Matrix44A m_prevViewProj[4];

	Vec4 vWorldBasisX, vWorldBasisY, vWorldBasisZ;

	CTexture *m_pDiffuseAccRT;
	CTexture *m_pSpecularAccRT;
	CTexture *m_pNormalsRT;
	CTexture *m_pDepthRT;
	CTexture *m_pSceneTexturesRT;
	CTexture *m_pMSAAMaskRT;

	int m_nRenderState;
	uint32 m_nLightsProcessedCount;

	uint m_nCurrentShadowPoolLight;
	uint m_nFirstCandidateShadowPoolLight;
	uint m_nShadowPoolSize;
	bool m_bClearPool;

	bool m_bSpecularState;
	int m_nCurLighID;
	short m_nCurTargetWidth;
	short m_nCurTargetHeight;
	static CDeferredShading* m_pInstance;
	//static CTexPoolAtlas m_blockPack;
	static CPowerOf2BlockPacker m_blockPack;
	static TArray<SShadowAllocData> m_shadowPoolAlloc;

public:
	static CDeferredShading* CreateDeferredShading()
	{
		m_pInstance = new CDeferredShading();

		return m_pInstance;
	}

	static void DestroyDeferredShading()
	{
		SAFE_DELETE(m_pInstance);
	}

	SDynTexture* m_pSSAORT;
};

class CTexPoolAtlas
{
public:
	CTexPoolAtlas()
	{
		m_nSize = 0;
#ifdef _DEBUG
		m_nTotalWaste = 0;
#endif
	}
	~CTexPoolAtlas()    {}
	void Init(int nSize);
	void Clear();
	void FreeMemory();
	bool AllocateGroup(int32* pOffsetX, int32* pOffsetY, int nSizeX, int nSizeY);

	int m_nSize;
	static const int MAX_BLOCKS = 128;
	uint32 m_arrAllocatedBlocks[MAX_BLOCKS];

#ifdef _DEBUG
	uint32 m_nTotalWaste;
	struct SShadowMapBlock
	{
		uint16 m_nX1, m_nX2, m_nY1, m_nY2;
		bool Intersects(const SShadowMapBlock & b) const
		{
			return max(m_nX1, b.m_nX1) < min(m_nX2, b.m_nX2)
				&& max(m_nY1, b.m_nY1) < min(m_nY2, b.m_nY2);
		}
	};
	std::vector<SShadowMapBlock> m_arrDebugBlocks;

	void _AddDebugBlock(int x, int y, int sizeX, int sizeY);
	float _GetDebugUsage() const;
#endif
};

#endif
